﻿using System;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Imaging;
using System.Drawing.Drawing2D;
using System.IO;

using Kusog.SiteStorage;

namespace Kusog.Media
{
    public class ImageProcessor
    {

        public Binary createScaledImage(Binary imgBin, string originalName, Scaling imgAdjInfo)
        {
            bool hasOverlay = !string.IsNullOrWhiteSpace(imgAdjInfo.OverlayImage);
            Bitmap[] imgs = new Bitmap[hasOverlay ? 2 : 1];
            Binary[] bins = new Binary[hasOverlay ? 2 : 1];
            bins[0] = imgBin;
            imgs[0] = (Bitmap)Bitmap.FromStream(imgBin.DataStream, true, false);

            OverlayPosition overlayPos = OverlayPosition.expand;
            if (hasOverlay)
            {
                bins[1] = retrieveImageStream(imgAdjInfo.OverlayImage);
                if (bins[1] != null)
                    imgs[1] = (Bitmap)Bitmap.FromStream(bins[1].DataStream, true, false);
                else
                    return null;
                if (!string.IsNullOrWhiteSpace(imgAdjInfo.OverlayAlign))
                    overlayPos = (OverlayPosition)Enum.Parse(typeof(OverlayPosition), imgAdjInfo.OverlayAlign);
            }

            int desiredWidth = (imgAdjInfo.Width <= 0) ? 0 : imgAdjInfo.Width;
            int desiredHeight = (imgAdjInfo.Height <= 0) ? 0 : imgAdjInfo.Height;

            Rectangle overlayOpeningRect = new Rectangle(), newImageSize = new Rectangle();
            if (hasOverlay)
            {
                if (imgAdjInfo.OverlayOpeningRect.HasValue)
                {
                    newImageSize = calcRect(desiredWidth, desiredHeight, imgs[1].Width, imgs[1].Height, false);
                    overlayOpeningRect = imgAdjInfo.OverlayOpeningRect.Value;
                }
            }
            if (newImageSize.Width == 0)
                newImageSize = calcRect(desiredWidth, desiredHeight, imgs[0].Width, imgs[0].Height, false);

            Bitmap newImage = new Bitmap(newImageSize.Width, newImageSize.Height);
            Graphics g = Graphics.FromImage(newImage);

            //TODO: Make background fill an option for image adjustment. They can spec color.
            //g.FillRectangle(Brushes.White, new Rectangle(0, 0, width, height));
            g.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBicubic;


            for (int pos = 0; pos < imgs.Length; pos++)
            //while (++curAttempt <= maxAttempts)
            {

                try
                {
                    Bitmap origImage = imgs[pos];
                    Rectangle sourceRect, destRect;
                    if (pos == 0 && overlayOpeningRect.Width > 0)
                    {
                        int newOverlayWidth = (newImage.Width * overlayOpeningRect.Width) / imgs[1].Width;
                        int newOverlayHeight = (newImage.Height * overlayOpeningRect.Height) / imgs[1].Height;
                        int newOverlayX = (overlayOpeningRect.X * newImage.Width) / imgs[1].Width;
                        int newOverlayY = (overlayOpeningRect.Y * newImage.Height) / imgs[1].Height;
                        sourceRect = calcSourceRect(newOverlayWidth, newOverlayHeight, origImage.Width, origImage.Height);
                        destRect = new Rectangle(newOverlayX, newOverlayY, newOverlayWidth, newOverlayHeight);
                        Matrix mx = null;
                        if (imgAdjInfo.OverlayRotate != 0)
                        {
                            mx = new Matrix();
                            mx.RotateAt(imgAdjInfo.OverlayRotate, new Point(newOverlayX, newOverlayY));
                            g.Transform = mx;
                        }
                    }
                    else if (pos > 0 && overlayPos != OverlayPosition.expand)
                    {
                        sourceRect = new Rectangle(0, 0, origImage.Width, origImage.Height);
                        destRect = new Rectangle(0, 0, newImage.Width, newImage.Height);
                        switch (overlayPos)
                        {
                            case OverlayPosition.bottom:
                                int h = (newImage.Width * origImage.Height) / origImage.Width;
                                destRect = new Rectangle(0, newImage.Height - h, newImage.Width, h);
                                break;
                            case OverlayPosition.left:
                                int w = ((newImage.Height + 1) * origImage.Width) / origImage.Height;
                                destRect = new Rectangle(-1, -1, w, newImage.Height + 1);
                                break;
                            case OverlayPosition.top:
                                h = ((newImage.Width + 1) * origImage.Height) / origImage.Width;
                                destRect = new Rectangle(-1, -1, newImage.Width + 1, h);
                                break;
                            case OverlayPosition.right:
                                w = ((newImage.Height + 1) * origImage.Width) / origImage.Height;
                                destRect = new Rectangle(newImage.Width - 1 - w, -1, w, newImage.Height + 1);
                                break;
                        }
                    }
                    else
                    {
                        sourceRect = calcSourceRect(newImage.Width, newImage.Height, origImage.Width, origImage.Height);
                        destRect = new Rectangle(0, 0, newImage.Width, newImage.Height);
                    }
                    g.DrawImage(origImage, destRect, sourceRect, GraphicsUnit.Pixel);
                    g.Transform = new Matrix();

                    if (origImage.Palette != null && origImage.Palette.Entries.Length > 0)
                        newImage.Palette = origImage.Palette;
                }
                catch { }
            }

            if (!string.IsNullOrWhiteSpace(imgAdjInfo.WatermarkText))
                addWatermarkText(newImage, g, imgAdjInfo);
            if (!string.IsNullOrWhiteSpace(imgAdjInfo.WatermarkImage))
                addWatermarkImage(newImage, g, imgAdjInfo);

            EncoderParameters eps = getDefaultEncoderParameters(imgAdjInfo.Quality);

            Binary rc = saveImage(newImage, originalName, imgAdjInfo, eps);

            eps.Dispose();
            g.Dispose();
            newImage.Dispose();
            foreach (Bitmap bm in imgs)
                bm.Dispose();
            foreach (Binary b in bins)
                b.Dispose();

            return rc;
        }

        protected MediaStream retrieveImageStream(string name)
        {
            return null;
        }

        protected Binary saveImage(Bitmap image, string name, Scaling imgAdjInfo, EncoderParameters eps)
        {
            Binary bin = new Binary();
            image.Save(bin.DataStream, getDesiredEncoder(Path.GetExtension(name).Substring(1)), eps);
            return bin;
        }


        protected Rectangle calcRect(int desiredWidth, int desiredHeight, int origWidth, int origHeight, bool forceScale)
        {
            Rectangle calcRect = new Rectangle(0, 0, desiredWidth, desiredHeight);
            if (forceScale || desiredWidth == 0 || desiredHeight == 0)
            {
                int width = desiredWidth;
                int height = desiredHeight;
                double heightRatio = (double)origHeight / origWidth;
                double widthRatio = (double)origWidth / origHeight;

                if (width != 0)
                    height = Convert.ToInt32(width * heightRatio);

                if (height <= desiredHeight)
                {
                    height = desiredHeight;
                    width = Convert.ToInt32(height * widthRatio);
                }
                calcRect.Width = width;
                calcRect.Height = height;
            }
            return calcRect;
        }

        protected Rectangle calcSourceRect(int newWidth, int newHeight, int origWidth, int origHeight)
        {
            Rectangle naturalSize = calcRect(newWidth, newHeight, origWidth, origHeight, true);
            Rectangle sourceRect = new Rectangle(0, 0, origWidth, origHeight);

            if (naturalSize.Height > newHeight)
            {
                int dif = (naturalSize.Height - newHeight) / 2;
                int origDif = ((origHeight * dif) / naturalSize.Height);
                sourceRect = new Rectangle(0, origDif, origWidth, origHeight - (origDif * 2));
            }
            else if (naturalSize.Width != newWidth)
            {
                int dif = (naturalSize.Width - newWidth) / 2;
                int origDif = ((origWidth * dif) / naturalSize.Width);
                sourceRect = new Rectangle(origDif, 0, origWidth - (origDif * 2), origHeight);
            }

            return sourceRect;
        }

        protected Rectangle calcDestRect(int newWidth, int newHeight, Rectangle origRect)
        {
            Rectangle dest = new Rectangle(0, 0, newWidth, newHeight);
            return dest;
        }

        protected EncoderParameters getDefaultEncoderParameters(long quality)
        {
            EncoderParameters eps = new EncoderParameters(1);
            eps = new EncoderParameters();
            eps.Param[0] = new EncoderParameter(Encoder.Quality, quality);
            return eps;
        }

        protected virtual System.Drawing.Imaging.ImageCodecInfo getDesiredEncoder(string imageSubType)
        {
            if (string.Compare("gif", imageSubType, true) == 0)
                imageSubType = "png";
            return getEncoderInfo(MediaStream.calcMimeType(imageSubType));
        }

        /// <summary>
        /// Gets the encoder information for the specified mimetype.  Used in imagescaling
        /// </summary>
        /// <param name="mimeType">The mimetype of the picture.</param>
        /// <returns>System.Drawing.Imaging.ImageCodecInfo</returns>
        public static System.Drawing.Imaging.ImageCodecInfo getEncoderInfo(string mimeType)
        {
            System.Drawing.Imaging.ImageCodecInfo[] myEncoders =
                System.Drawing.Imaging.ImageCodecInfo.GetImageEncoders();

            foreach (System.Drawing.Imaging.ImageCodecInfo myEncoder in myEncoders)
                if (myEncoder.MimeType == mimeType)
                    return myEncoder;
            return null;
        }

        /// <summary>
        /// Will modify the given Bitmap by adding semi-transparent text from the sizing options
        /// over the image.  The result is the origional image with a "watermark" in order to help
        /// identify something about it.
        /// </summary>
        /// <param name="img"></param>
        /// <param name="imgG"></param>
        /// <param name="sizing"></param>
        protected void addWatermarkText(Bitmap img, Graphics imgG, Scaling imgAdjInfo)
        {
            int[] sizes = new int[] { 16, 14, 12, 10, 8, 6, 4 };
            Font crFont = null;
            SizeF crSize = new SizeF();
            for (int i = 0; i < sizes.Length; ++i)
            {
                crFont = new Font("arial", sizes[i], FontStyle.Bold);
                crSize = imgG.MeasureString(imgAdjInfo.WatermarkText, crFont);

                if ((ushort)crSize.Width < (ushort)img.Width)
                    break;
            }

            float xpos = 0;
            float ypos = 0;

            WatermarkPosition pos = (WatermarkPosition)Enum.
                Parse(typeof(WatermarkPosition), imgAdjInfo.WatermarkPos, true);
            switch (pos)
            {
                case WatermarkPosition.topLeft:
                    xpos = ((float)img.Width * (float).01) + (crSize.Width / 2);
                    ypos = (float)img.Height * (float).01;
                    break;
                case WatermarkPosition.topRight:
                    xpos = ((float)img.Width * (float).99) - (crSize.Width / 2);
                    ypos = (float)img.Height * (float).01;
                    break;
                case WatermarkPosition.bottomRight:
                    xpos = ((float)img.Width * (float).99) - (crSize.Width / 2);
                    ypos = ((float)img.Height * (float).99) - crSize.Height;
                    break;
                case WatermarkPosition.bottomLeft:
                    xpos = ((float)img.Width * (float).01) + (crSize.Width / 2);
                    ypos = ((float)img.Height * (float).99) - crSize.Height;
                    break;
            }

            StringFormat StrFormat = new StringFormat();
            StrFormat.Alignment = StringAlignment.Center;

            SolidBrush semiTransBrush2 = new SolidBrush(Color.FromArgb(153, 0, 0, 0));
            imgG.DrawString(imgAdjInfo.WatermarkText, crFont, semiTransBrush2, xpos + 1, ypos + 1, StrFormat);

            SolidBrush semiTransBrush = new SolidBrush(Color.FromArgb(153, 255, 255, 255));
            imgG.DrawString(imgAdjInfo.WatermarkText, crFont, semiTransBrush, xpos, ypos, StrFormat);


            semiTransBrush2.Dispose();
            semiTransBrush.Dispose();
        }

        /// <summary>
        /// With modify the given Bitmap by adding a semi-transparent image from the sizing
        /// options over it.  The result is the origional image with a "watermark" to help
        /// identify something about it.
        /// </summary>
        /// <param name="img"></param>
        /// <param name="imgG"></param>
        /// <param name="sizing"></param>
        protected void addWatermarkImage(Bitmap img, Graphics imgG, Scaling imgAdjInfo)
        {
            Image watermark = new Bitmap(imgAdjInfo.WatermarkImage);

            ImageAttributes imageAttributes = new ImageAttributes();
            ColorMap colorMap = new ColorMap();

            colorMap.OldColor = Color.FromArgb(255, 0, 255, 0);
            colorMap.NewColor = Color.FromArgb(0, 0, 0, 0);
            ColorMap[] remapTable = { colorMap };

            imageAttributes.SetRemapTable(remapTable, ColorAdjustType.Bitmap);

            float[][] colorMatrixElements = {
												new float[] {1.0f,  0.0f,  0.0f,  0.0f, 0.0f},
												new float[] {0.0f,  1.0f,  0.0f,  0.0f, 0.0f},
												new float[] {0.0f,  0.0f,  1.0f,  0.0f, 0.0f},
												new float[] {0.0f,  0.0f,  0.0f,  0.3f, 0.0f},
												new float[] {0.0f,  0.0f,  0.0f,  0.0f, 1.0f}
											};

            ColorMatrix colorMatrix = new ColorMatrix(colorMatrixElements);

            imageAttributes.SetColorMatrix(colorMatrix, ColorMatrixFlag.Default,
                ColorAdjustType.Bitmap);

            int xpos = 0;
            int ypos = 0;

            WatermarkPosition pos = (WatermarkPosition)Enum.
                Parse(typeof(WatermarkPosition), imgAdjInfo.WatermarkPos, true);
            switch (pos)
            {
                case WatermarkPosition.topLeft:
                    xpos = 10;
                    ypos = 10;
                    break;
                case WatermarkPosition.topRight:
                    xpos = ((img.Width - watermark.Width) - 10);
                    ypos = 10;
                    break;
                case WatermarkPosition.bottomRight:
                    xpos = ((img.Width - watermark.Width) - 10);
                    ypos = img.Height - watermark.Height - 10;
                    break;
                case WatermarkPosition.bottomLeft:
                    xpos = 10;
                    ypos = img.Height - watermark.Height - 10;
                    break;
            }

            imgG.DrawImage(watermark, new Rectangle(xpos, ypos, watermark.Width, watermark.Height),
                0, 0, watermark.Width, watermark.Height, GraphicsUnit.Pixel, imageAttributes);

            watermark.Dispose();
            imageAttributes.Dispose();
        }
    }

    public enum ImageData
    {
        imageDataOnly,
        exifDataOnly,
        allData
    }
    public enum WatermarkPosition { topLeft, topRight, bottomLeft, bottomRight, center }
    public enum OverlayPosition { expand, left, right, top, bottom };
    public enum ImageInfoType { asRelativeName, asImageObj, asMediaStream };
    public enum ImageListType
    {
        /// <summary>
        /// Gets only the files that match the pattern passed from the base image sorce directory.
        /// Child directories are simply ignored.
        /// </summary>
        filesOnly,
        /// <summary>
        /// Similar to the filesOnly option, but includes directories as though they were images in
        /// the source directory.
        /// </summary>
        filesWithDirsAsImages,
        /// <summary>
        /// Gets only the files that match the pattern passed from the base image source
        /// directory and all of its child directories recursively.  Every image in all of the
        /// directories within the tree of directories below it are included.  The name of the
        /// image will contain a relative name with the child directories embedded in it. 
        /// </summary>
        deepFiles
    };
}
